#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <time.h>
#include <chrono>
#include "spdlog/spdlog.h"
#include "spdlog/async.h"
#include "spdlog/sinks/stdout_color_sinks.h" // or "../stdout_sinks.h" if no color needed
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "common/module_path.h"

static inline int NowDateToInt()
{
	time_t now;
	time(&now);
 
	// choose thread save version in each platform
	tm p;
#ifdef _WIN32
	localtime_s(&p, &now);
#else
	localtime_r(&now, &p);
#endif // _WIN32
	int now_date = (1900 + p.tm_year) * 10000 + (p.tm_mon + 1) * 100 + p.tm_mday;
	return now_date;
}
 
static inline int NowTimeToInt()
{
	time_t now;
	time(&now);
	// choose thread save version in each platform
	tm p;
#ifdef _WIN32
	localtime_s(&p, &now);
#else
	localtime_r(&now, &p);
#endif // _WIN32
 
	int now_int = p.tm_hour * 10000 + p.tm_min * 100 + p.tm_sec;
	return now_int;
}
 
class XLogger
{
public:
	static XLogger* getInstance()
	{
		static XLogger xlogger;
		return &xlogger;
	}
 
	std::shared_ptr<spdlog::logger> getLogger()
	{
		return m_logger;
	}
 
private:
	// make constructor private to avoid outside instance
	XLogger()
	{
		// hardcode log path
		const std::string log_dir = ApplicationInfo::getApplicationPath() + "log"; // should create the folder if not exist
		const std::string logger_name_prefix = /*getExecName()*/ApplicationInfo::getApplicationName();
 
		// decide print to console or log file
		bool console = false;
 
		// decide the log level
 		spdlog::level::level_enum level(spdlog::level::debug);
		try
		{
			// logger name with timestamp
			int date = NowDateToInt();
			int time = NowTimeToInt();
			const std::string logger_name = logger_name_prefix; // + std::to_string(date) + "_" + std::to_string(time);
 
			if (console)
				m_logger = spdlog::stdout_color_st(logger_name); // single thread console output faster
			else
				//m_logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>(logger_name, log_dir + "/" + logger_name + ".log"); // only one log file
				m_logger = spdlog::create_async<spdlog::sinks::rotating_file_sink_mt>(logger_name, log_dir + "/" + logger_name + ".log", 20 * 1024 * 1024, 1000); // multi part log files, with every part 500M, max 1000 files
 
			// custom format, https://cloud.tencent.com/developer/article/2102105
			m_logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [thread %t] [%l] [%s:%#] %v"); // with timestamp, thread_id, filename and line number
			m_logger->set_level(level);
			m_logger->flush_on(level);			
		}
		catch (const spdlog::spdlog_ex& ex)
		{
			std::cout << "Log initialization failed: " << ex.what() << std::endl;
		}
	}
 
	~XLogger()
	{
		spdlog::drop_all(); // must do this
	}
 
	void* operator new(size_t size)
	{}
 
	XLogger(const XLogger&) = delete;
	XLogger& operator=(const XLogger&) = delete;
 
private:
	std::shared_ptr<spdlog::logger> m_logger;
};


class LogStreamHelper : public std::ostringstream
{
public:
    static std::shared_ptr<std::ostream>    stream(std::shared_ptr<spdlog::logger> logger, spdlog::level::level_enum prio, const char *file, int line) {
		return std::shared_ptr<std::ostream>(new LogStreamHelper(logger, prio, file, line));
	}

	~LogStreamHelper()
    {
        if (logger_->should_log(priority_)) {
			logger_->log(spdlog::source_loc{file_, line_, SPDLOG_FUNCTION}, priority_, str());
        }
    }

private:
    explicit        LogStreamHelper(std::shared_ptr<spdlog::logger> logger, spdlog::level::level_enum prio, const char *file, int line)
        : std::ostringstream(),
          logger_(logger),
          priority_(prio),
          file_(file),
          line_(line)
    {}

    std::shared_ptr<spdlog::logger>        logger_;
    spdlog::level::level_enum              priority_;
    const char                  *file_;
    int                         line_;
};

// use embedded macro to support file and line number
#define XLOG_TRACE(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLogger().get(), spdlog::level::trace, __VA_ARGS__)
#define XLOG_DEBUG(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLogger().get(), spdlog::level::debug, __VA_ARGS__)
#define XLOG_INFO(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLogger().get(), spdlog::level::info, __VA_ARGS__)
#define XLOG_WARN(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLogger().get(), spdlog::level::warn, __VA_ARGS__)
#define XLOG_ERROR(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLogger().get(), spdlog::level::err, __VA_ARGS__)

#define SPD_LOG(priority) (*LogStreamHelper::stream(XLogger::getInstance()->getLogger(), priority, __FILE__, __LINE__))
#define LOG_TRACE()        SPD_LOG(spdlog::level::trace)
#define LOG_DEBUG()        SPD_LOG(spdlog::level::debug)
#define LOG_INFO()         SPD_LOG(spdlog::level::info)
#define LOG_WARN()         SPD_LOG(spdlog::level::warn)
#define LOG_ERROR()        SPD_LOG(spdlog::level::err)

 